/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

package utils

import (
	"reflect"
	"slices"
	"strconv"
	"strings"
)

// OrderedListFromMap generates a list from a map's values. The values are sorted based on the map's keys.
// The sorting is done using a custom comparison function that compares the keys with special rules, assuming they
// are strings representing device names or similar, i.e. "disk0", "net1", etc.
func OrderedListFromMap(inputMap map[string]any) []any {
	itemCount := len(inputMap)
	keyList := make([]string, itemCount)
	i := 0

	for key := range inputMap {
		keyList[i] = key
		i++
	}

	slices.SortFunc(keyList, CompareWithPrefix)

	return OrderedListFromMapByKeyValues(inputMap, keyList)
}

// CompareWithPrefix compares two string values with special rules:
// - If both start with the same prefix, trims the prefix and compares the rest as numbers if possible.
// - If numbers are equal, falls back to string comparison (preserving digit formatting).
// - If numeric parsing fails, falls back to string comparison.
// - If prefixes differ, compares the whole values as strings.
func CompareWithPrefix(a, b string) int {
	prefix := commonPrefix(a, b)

	if prefix != "" {
		aRest := strings.TrimPrefix(a, prefix)
		bRest := strings.TrimPrefix(b, prefix)

		aNum, aErr := strconv.Atoi(aRest)

		bNum, bErr := strconv.Atoi(bRest)
		if aErr == nil && bErr == nil {
			if aNum != bNum {
				if aNum < bNum {
					return -1
				}

				return 1
			}
			// numeric values equal, fallback to string comparison
			return strings.Compare(aRest, bRest)
		}

		return strings.Compare(aRest, bRest)
	}

	return strings.Compare(a, b)
}

// commonPrefix returns the longest common prefix of two strings.
func commonPrefix(a, b string) string {
	minLen := min(len(b), len(a))

	for i := range minLen {
		if a[i] != b[i] {
			return a[:i]
		}
	}

	return a[:minLen]
}

// ListResourcesAttributeValue generates a list of strings from a Terraform resource list (which is list of maps).
// The list is generated by extracting a specific key attribute from each resource. If the attribute is not found in a
// resource, it is skipped.
func ListResourcesAttributeValue(resourceList []any, keyAttr string) []string {
	var l []string

	for _, resource := range resourceList {
		if resource == nil {
			continue
		}

		r := resource.(map[string]any)
		if value, ok := r[keyAttr].(string); ok {
			l = append(l, value)
		}
	}

	return l
}

// MapResourcesByAttribute generates a map of resources from a resource list, using a specified attribute as the key
// and the resource as the value. If the attribute is not found in a resource, it is skipped.
func MapResourcesByAttribute(resourceList []any, keyAttr string) map[string]any {
	m := make(map[string]any, len(resourceList))

	for _, resource := range resourceList {
		if resource == nil {
			continue
		}

		r := resource.(map[string]any)
		if key, ok := r[keyAttr].(string); ok {
			m[key] = r
		}
	}

	return m
}

// OrderedListFromMapByKeyValues generates a list from a map's values.
// The values are sorted based on the provided key list. If a key is not found in the map, it is skipped.
func OrderedListFromMapByKeyValues(inputMap map[string]any, keyList []string) []any {
	orderedList := make([]any, len(keyList))

	for i, k := range keyList {
		val, ok := inputMap[k]
		if ok {
			orderedList[i] = val
		}
	}

	return orderedList
}

// MapDiff compares the difference between two maps and returns the elements that are in the plan but not
// in the state (toCreate), the elements that are in the plan and in the state but are different (toUpdate),
// and the elements that are in the state but not in the plan (toDelete).
// The keyFunc is used to extract a unique key from each element to compare them.
func MapDiff[T any](plan map[string]T, state map[string]T) (map[string]T, map[string]T, map[string]T) {
	toCreate := map[string]T{}
	toUpdate := map[string]T{}
	toDelete := map[string]T{}

	for key, p := range plan {
		s, ok := state[key]
		if !ok {
			toCreate[key] = p
		} else if !reflect.DeepEqual(p, s) {
			toUpdate[key] = p
		}
	}

	for key, s := range state {
		_, ok := plan[key]
		if !ok {
			toDelete[key] = s
		}
	}

	return toCreate, toUpdate, toDelete
}
